package com.wh1200.mybatis.xmlless.config

import com.wh1200.mybatis.xmlless.AnnotatedElementDetailInfo
import com.wh1200.mybatis.xmlless.constant.ID_NUMBER_TYPES
import com.wh1200.mybatis.xmlless.enums.XmllessFieldStrategy
import com.wh1200.mybatis.xmlless.model.*
import com.wh1200.mybatis.xmlless.resolver.MappingResolver
import com.wh1200.mybatis.xmlless.resolver.TypeResolver
import net.sf.jsqlparser.schema.Column
import net.sf.jsqlparser.schema.Table

/**
 * @author 吴昊
 * @date 2018/12/14
 * @version 1.0
 * @since v1.0.0
 */
class MappingResolverProxy {

  private val mappingCache = hashMapOf<String, FieldMappingList>()


  fun getAllMappings(): List<FieldMappingList> {
    return mappingCache.values.toList()
  }

  fun getMappingCache(modelClassName: String): FieldMappingList? {
    return mappingCache[modelClassName]
  }

  fun getMappingCache(modelClass: Class<*>): FieldMappingList? {
    return mappingCache[modelClass.name]
  }

  fun resolveNonEntityClass(
      classInfo: ClassInfo,
      modelClassName: String,
      tableInfo: XmllessTableInfo,
      currentTableInfo: XmllessTableInfo
  ): FieldMappingList {
    if (MappingResolver.getMappingCache(classInfo.fullName) != null) {
      return MappingResolver.getMappingCache(classInfo.fullName)!!
    }
    val modelFieldInfoMap = tableInfo.tableFieldMap
    val currentFieldInfoMap = currentTableInfo.tableFieldMap

    val fields = classInfo.fields.filter {
      it.name in modelFieldInfoMap || it.name in currentFieldInfoMap
    }
    val mapping = FieldMappingList(fields.map { field ->
      val fieldInfo = modelFieldInfoMap[field.name] ?: currentFieldInfoMap[field.name] as XmllessTableFieldInfo
      val joinInfo = resolveJoinInfo(fieldInfo, modelClassName)
      createFieldMapping(
          field, tableInfo,
          fieldInfo, joinInfo, isKeyField = fieldInfo.property == tableInfo.keyProperty,
          joinObjects = field.getJoinObjects()
      )
    }, tableInfo, classInfo.fullName)
    mappingCache[classInfo.fullName] = mapping
    return mapping
  }

  fun resolve(
      tableInfo: XmllessTableInfo,
      useCache: Boolean
  ): FieldMappingList {
    if (MappingResolver.getMappingCache(tableInfo.entityTypeName) != null && useCache) {
      return MappingResolver.getMappingCache(tableInfo.entityTypeName)!!
    }
    val fieldInfoMap = tableInfo.tableFieldMap
    val fields = tableInfo.classFields.filter {
      it.name in fieldInfoMap
    }
    val mapping = FieldMappingList(fields.map { field ->
      val fieldInfo = fieldInfoMap[field.name] as XmllessTableFieldInfo
      val joinInfo = resolveJoinInfo(fieldInfo, tableInfo.entityTypeName)
      createFieldMapping(
          field, tableInfo,
          fieldInfo, joinInfo, isKeyField = fieldInfo.property == tableInfo.keyProperty,
          joinObjects = field.getJoinObjects()
      )
    }, tableInfo, tableInfo.entityTypeName)
    mappingCache[tableInfo.entityTypeName] = mapping
    return mapping
  }

  private fun createFieldMapping(
      field: AnnotatedElementDetailInfo,
      tableInfo: XmllessTableInfo,
      fieldInfo: XmllessTableFieldInfo,
      joinInfo: JoinInfo?,
      isKeyField: Boolean,
      joinObjects: List<JoinObjectInfo>
  ): FieldMapping {
    val isJsonObject = isJsonObject(tableInfo, field)
    val genericType = field.genericType
    val realType = TypeResolver.resolveRealType(field.genericType)
    return FieldMapping(
        fieldInfo,
        joinInfo, isKeyField,
        joinObjects,
        field.isCount,
        field.isInsertIgnore || fieldInfo.insertStrategy == XmllessFieldStrategy.NEVER,
        field.isAutoGeneratedKey,
        field.name,
        field.isSelectIgnore,
        genericType,
        genericType in ID_NUMBER_TYPES,
        realType == String::class.java || realType.isEnum,
        Number::class.java.isAssignableFrom(realType),
        field.isUpdateIgnore || fieldInfo.updateStrategy == XmllessFieldStrategy.NEVER,
        isJsonObject,
        isJsonObject && field.isCollection,
        field.handlerName,
        field.handler,
        field.isVersion
    )
  }

  private fun isJsonObject(tableInfo: XmllessTableInfo, field: AnnotatedElementDetailInfo): Boolean {
    val modelMapping = MappingResolver.getMappingCache(tableInfo.entityTypeName)
    if (modelMapping != null) {
      val mapping = modelMapping.mappings.find { it.property == field.name
          && it.type == field.genericType}
      if (mapping != null) {
        return mapping.isJsonObject
      }
    }
    return field.isJsonMappingProperty
  }

  private fun resolveJoinInfo(
      fieldInfo: XmllessTableFieldInfo,
      modelClassName: String
  ): JoinInfo? {
    val joinInfo = resolveJoin(fieldInfo)
    if (joinInfo is ObjectJoinInfo) {
      val joinTableInfo = joinInfo.joinTableInfo!!
      // 防止无限循环
      if (joinInfo.joinEntityName != modelClassName) {
        MappingResolver.resolve(joinTableInfo)
      }
    }
    return joinInfo
  }

  private fun resolveJoin(fieldInfo: XmllessTableFieldInfo): JoinInfo? {
    val joinTableColumn = fieldInfo.joinTableColumn
    val joinObject = fieldInfo.joinObject
    val count = fieldInfo.count
    return when {
      joinTableColumn != null    -> joinTableColumn.let {
        val targetTable = TableName.resolveWithPrefix(it.table, null)
        PropertyJoinInfo(
            null, ColumnName(it.columnMapTo, fieldInfo.property, fieldInfo.fieldWrapper),
            targetTable,
            it.joinType,
            Column(it.joinOnThisProperty),
            Column(Table(targetTable.getAliasOrName()), it.joinOnColumn),
            fieldInfo.genericType
        )
      }

      joinObject != null         -> joinObject.resolve()
      count != null              -> {
        val targetTableName = TableName.resolveWithPrefix(count.targetTable)
        PropertyJoinInfo(
            null,
            ColumnName("COUNT(${targetTableName.alias}.${count.countColumn})", fieldInfo.property, fieldInfo.fieldWrapper),
            targetTableName,
            count.joinType,
            Column(count.joinProperty),
            Column(Table(targetTableName.getAliasOrName()), count.targetColumn),
            fieldInfo.genericType,
            "${targetTableName.alias}.${count.countColumn}"
        )
      }

      else                       -> null
    }
  }

  fun clearCache() {
    mappingCache.clear()
  }

}
